<?php
require_once 'Junction/Core/Factory.php';
require_once 'Junction/Utils/Reflection.php';

class Junction_Tracker {

    private static $_log = array();
    
    public static function dumpLogs() {
        var_dump(self::$_log);
    }

    public static function hasBeenLogged($object) {
        if (!isset($object)) {
            return false;
        }
        return isset(self::$_log[self::getHash($object)]);
    }
    
    private function getHash($object) {
        $mapping = Junction_Core_Factory::construct($object);
        $hash = $mapping->getName() . $mapping->getValueFor($object, $mapping->getKeyProperty());
        return $hash;
    }

    /**
     * Log the state of the object for later comparison.
     *
     * <p>The changes are tracked per property.  For properties
     * which yield complex types recurse.
     *
     * @param Object $object
     */
    public static function trackChangesFor($object) {
        $mapping = Junction_Core_Factory::construct($object);
        foreach ($mapping->getProperties() as $property) {
            $value = Junction_Utils_Reflection::getValueFor($object, $property);
            if (self::isCollection($value)) {
                self::trackCollection($value);
            } elseif (is_object($value)) {
                self::trackChangesFor($value);
            } else {
                self::record($object, $property, $value);
            }
        }
    }

    private static function record($object, $property, $value) {
        $hash = self::getHash($object);
        self::$_log[$hash][$property] = $value;
    }

    private static function isCollection($object) {
        return (is_a($object, 'Traversable') || is_array($object));
    }

    private static function isObject($object) {
        return is_object($object);
    }

    private static function trackCollection($collection) {
        foreach ($collection as $item) {
            self::trackChangesFor($item);
        }
    }

    public static function hasPropertyChanged($object, $property, $value) {
        if (self::isCollection($value)) {
            return self::hasCollectionChanged($value);
        }
        if (self::isObject($value)) {
            return self::hasChanged($value);
        }
        return self::isChanged($object, $property, $value);
    }

    /**
     * Compare the object's state to its corresponding log.
     *
     * @param Object $object
     * @return boolean
     */
    public static function hasChanged($object) {
        $mapping = Junction_Core_Factory::construct($object);
        $changed = false;
        foreach ($mapping->getProperties() as $property) {
            $value = Junction_Utils_Reflection::getValueFor($object, $property);
            $changed = self::hasPropertyChanged($object, $property, $value);
        }
        return $changed;
    }
    
    public static function hasShallowChanges($object) {
        $mapping = Junction_Core_Factory::construct($object);
        $changed = false;
        foreach ($mapping->getScalarProperties() as $property) {
            $value = Junction_Utils_Reflection::getValueFor($object, $property);
            $changed = self::hasPropertyChanged($object, $property, $value);
        }
        return $changed;
    }
    
    public static function hasDeepChanges($object) {
        $mapping = Junction_Core_Factory::construct($object);
        $changed = false;
        foreach ($mapping->getReferences() as $property => $ref) {
            $value = Junction_Utils_Reflection::getValueFor($object, $property);
            $changed = self::hasPropertyChanged($object, $property, $value);
        }
        return $changed;
    }

    private static function hasCollectionChanged($collection) {
        $changed = false;
        foreach ($collection as $item) {
            $changed = self::hasChanged($item);
        }
        return $changed;
    }

    private function isChanged($object, $property, $value) {
        if (!self::hasBeenLogged($object)) {
            return true;
        }
        if (!isset(self::$_log[spl_object_hash($object)][$property])) {
            return true;
        }
        return !(self::$_log[spl_object_hash($object)][$property] == $value);
    }
}
?>